#! /bin/bash

# @file   update_install
# @author Nat Goodspeed
# @date   2013-01-09
# @brief  Update the containing Second Life application bundle to the version in
#         the specified tarball.
# 
#         This bash implementation is derived from the previous linux-updater.bin
#         application.
# 
# $LicenseInfo:firstyear=2013&license=viewerlgpl$
# Copyright (c) 2013, Linden Research, Inc.
# $/LicenseInfo$

# ****************************************************************************
#   script parameters
# ****************************************************************************
tarball="$1"                        # the file to install
markerfile="$2"                     # create this file on failure
mandatory="$3"                      # what to write to markerfile on failure

# ****************************************************************************
#   helper functions
# ****************************************************************************
# empty array
cleanups=()

# add a cleanup action to execute on exit
function cleanup {
    # wacky bash syntax for appending to array
    cleanups[${#cleanups[*]}]="$*"
}

# called implicitly on exit
function onexit {
    for action in "${cleanups[@]}"
    do # don't quote, support actions consisting of multiple words
       $action
    done
}
trap 'onexit' EXIT

# write to log file
function log {
    # our log file will be open as stderr -- but until we set up that
    # redirection, logging to stderr is better than nothing
    echo "$*" 1>&2
}

# We display status by leaving one background xmessage process running. This
# is the pid of that process.
statuspid=""

function clear_message {
    [ -n "$statuspid" ] && kill $statuspid
    statuspid=""
}

# make sure we remove any message box we might have put up
cleanup clear_message

# can we use zenity, or must we fall back to xmessage?
zenpath="$(which zenity)"
if [ -n "$zenpath" ]
then # zenity on PATH and is executable
     # display a message box and continue
     function status {
         # clear any previous message
         clear_message
         # put up a new zenity box and capture its pid
         "$zenpath" --info --title "Second Life Viewer Updater" \
                    --width=320 --height=120 --text="$*" &
         statuspid=$!
     }

     # display an error box and wait for user
     function errorbox {
         "$zenpath" --error --title "Second Life Viewer Updater" \
                    --width=320 --height=120 --text="$*"
     }

else # no zenity, use xmessage instead
     # display a message box and continue
     function status {
         # clear any previous message
         clear_message
         # put up a new xmessage and capture its pid
         xmessage -buttons OK:2 -center "$*" &
         statuspid=$!
     }

     # display an error box and wait for user
     function errorbox {
         xmessage -buttons OK:2 -center "$*"
     }
fi

# display an error box and terminate
function fail {
    # Log the message
    log "$@"
    # tell subsequent viewer things went south
    echo "$mandatory" > "$markerfile"
    # add boilerplate
    errorbox "An error occurred while updating Second Life:
$*
Please download the latest viewer from www.secondlife.com."
    exit 1
}

# Find a graphical sudo program and define mysudo function. On error, $? is
# nonzero; output is in $err instead of being written to stdout/stderr.
gksudo="$(which gksudo)"
kdesu="$(which kdesu)"
if [ -n "$gksudo" ]
then function mysudo {
         # gksudo allows you to specify description
         err="$("$gksudo" --description "Second Life Viewer Updater" "$@" 2>&1)"
     }
elif [ -n "$kdesu" ]
then function mysudo {
         err="$("$kdesu" "$@" 2>&1)"
     }
else # couldn't find either one, just try it anyway
     function mysudo {
         err="$("$@" 2>&1)"
     }
fi

# Move directories, using mysudo if we think it necessary. On error, $? is
# nonzero; output is in $err instead of being written to stdout/stderr.
function sudo_mv {
    # If we have write permission to both parent directories, shouldn't need
    # sudo.
    if [ -w "$(dirname "$1")" -a -w "$(dirname "$2")" ]
    then err="$(mv "$@" 2>&1)"
    else # use available sudo program; mysudo sets $? and $err
         mysudo mv "$@"
    fi
}

# ****************************************************************************
#   main script logic
# ****************************************************************************
mydir="$(dirname "$0")"
# We happen to know that the viewer specifies a marker-file pathname within
# the logs directory.
logsdir="$(dirname "$markerfile")"
logname="$logsdir/updater.log"

# move aside old updater.log; we're about to create a new one
[ -f "$logname" ] && mv "$logname" "$logname.old"

# Set up redirections for this script such that stderr is logged. (But first
# move the previous stderr to file descriptor 3.)
exec 3>&2- 2> "$logname"

# Rather than setting up a special pipeline to timestamp every line of stderr,
# produce header lines into log file indicating timestamp and the arguments
# with which we were invoked.
date 1>&2
log "$0 $*"

# Log every command we execute, along with any stderr it might produce
set -x

status 'Installing Second Life...'

# Creating tempdir under /tmp means it's possible that tempdir is on a
# different filesystem than INSTALL_DIR. One is tempted to create tempdir on a
# path derived from `dirname INSTALL_DIR` -- but it seems modern 'mv' can
# handle moving across filesystems??
tempdir="$(mktemp -d)"
tempinstall="$tempdir/install"
# capture the actual error message, if any
err="$(mkdir -p "$tempinstall" 2>&1)" || fail "$err"
cleanup rm -rf "$tempdir"

# If we already knew the name of the tarball's top-level directory, we could
# just move that when all was said and done. Since we don't, untarring to the
# 'install' subdir with --strip 1 effectively renames that top-level
# directory.
# untar failures tend to be voluminous -- don't even try to capture, just log
tar --strip 1 -xjf "$tarball" -C "$tempinstall" || fail "Untar command failed"

INSTALL_DIR="$(cd "$mydir/.." ; pwd)"

# Considering we're launched from a subdirectory of INSTALL_DIR, would be
# surprising if it did NOT already exist...
if [ -e "$INSTALL_DIR" ]
then backup="$INSTALL_DIR.backup"
     backupn=1
     while [ -e "$backup" ]
     do backup="$INSTALL_DIR.backup.$backupn"
        ((backupn += 1))
     done
     # on error, fail with actual error message from sudo_mv: permissions,
     # cross-filesystem mv, ...?
     sudo_mv "$INSTALL_DIR" "$backup" || fail "$err"
fi
# We unpacked the tarball into tempinstall. Move that.
if ! sudo_mv "$tempinstall" "$INSTALL_DIR"
then # If we failed to move the temp install to INSTALL_DIR, try to restore
     # INSTALL_DIR from backup. Save $err because next sudo_mv will trash it!
     realerr="$err"
     sudo_mv "$backup" "$INSTALL_DIR"
     fail "$realerr"
fi

# Removing the tarball here, rather than with a 'cleanup' action, means we
# only remove it if we succeeded.
rm -f "$tarball"

# Launch the updated viewer. Restore original stderr from file descriptor 3,
# though -- otherwise updater.log gets cluttered with the viewer log!
"$INSTALL_DIR/secondlife" 2>&3- &
